using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Text;

namespace ConsoleApplication9
{
    public static class Func
    {
        public static TResult Apply<T, TResult>(T arg, Func<T,TResult> func)
        {
            return func(arg);
        }
    }

    public class Query<T>
    {
        public readonly SqlCommand SqlCommand;
        public Query(SqlCommand sqlCommand)
        {
            SqlCommand = sqlCommand;
        }
    }

    public struct Param<T>
    {
        public readonly Option<T> Value;
        public Param(Option<T> value)
        {
            Value = value;
        }
    }

    public static class SqlCommandExt
    {
        public static IEnumerable<T> GetEnumerable<T>(this Query<T> command)
        {
            using (var connection = new SqlConnection(""))
            {
                connection.Open();
                command.SqlCommand.Connection = connection;
                using (var reader = command.SqlCommand.ExecuteReader())
                    while (reader.Read())
                        yield return reader.Materialize<T>();
            }
        }

        public static IEnumerable<T> GetEnumerable<T>(this SqlCommand command)
        {
            using (var connection = new SqlConnection(""))
            {
                connection.Open();
                command.Connection = connection;
                using (var reader = command.ExecuteReader())
                    while (reader.Read())
                        yield return reader.Materialize<T>();
            }
        }        

    }

    public class QBuilder
    {
        public QBuilder Append(string s)
        {
            throw new NotImplementedException();
        }

        public QBuilder Append(Param<string> param)
        {
            var parameter = new SqlParameter {
                ParameterName = "p" + command.Parameters.Count,
                SqlDbType = SqlDbType.NVarChar,
                Size = -1
            };
            throw new NotImplementedException();
        }

        SqlCommand command;

        public QBuilder()
        {
            throw new NotImplementedException();
        }

        public QBuilder(string s)
        {
            throw new NotImplementedException();
        }

        public QBuilder Append<T>(string s, Option<Param<T>> param)
        {
            throw new NotImplementedException();
        }

        public QBuilder Append<T>(string s, Param<T> param)
        {
            throw new NotImplementedException();
        }

        public Query<T> Type<T>()
        {
            throw new NotImplementedException();
        }
    }

    public static class Util
    {
        public static Option<Param<T>> OptionalParam<T>(this T it, Func<T, bool> nothingPredicat)
        {
            return it.Nothing(nothingPredicat).Param();
        }

        public static Option<Param<T>> Param<T>(this Option<T> it)
        {
            return it.Select(_ => _.Param());
        }

        public static Param<T>? Param<T>(this T? it) where T: struct 
        {
            if (it.HasValue) return it.Value.Param();
            return null;
        }


        public static Param<T> Param<T>(this T it)
        {
            return new Param<T>(it);
        }

        public static Query<T> SetCommandText<T>(this SqlCommand it, string commandText)
        {
            if (!string.IsNullOrEmpty(it.CommandText)) throw new ApplicationException();
            it.CommandText = commandText;
            return new Query<T>(it);
        }

        public static Query<T> Query<T>(this SqlCommand it)
        {
            if (!string.IsNullOrEmpty(it.CommandText)) throw new ApplicationException();
            it.CommandText = commandText;
            return new Query<T>(it);
        }

        public static string Use(this Param<string> param, SqlCommand command)
        {
            Param<string> param1 = param;
            var parameter = new SqlParameter {
                ParameterName = "p" + command.Parameters.Count,
                SqlDbType = SqlDbType.NVarChar,
                Size = -1
            };
            if (param1.Value.HasValue) parameter.Value = param1.Value.Value;
            return command.Parameters.Add(parameter).ParameterName;
        }

        [CanGetParamValue]
        public static SqlParameter Add(this SqlCommand it, string name, Param<string> param)
        {
            var parameter = new SqlParameter {ParameterName = name, SqlDbType = SqlDbType.NVarChar, Size = -1};
            if (param.Value.HasValue) parameter.Value = param.Value.Value;
            return it.Parameters.Add(parameter);
        }

        [CanGetParamValue]
        public static SqlParameter Add(this SqlCommand it, string name, Param<int> param)
        {
            throw new NotImplementedException();
        }

        public static T Materialize<T>(this SqlDataReader reader)
        {
            throw new NotImplementedException();
        }
    }

    public class CanGetParamValueAttribute : Attribute
    {
    }

    public static class OptionExt
    {
        public static Option<T> Nothing<T>(this T it, Func<T, bool> func)
        {
            throw new NotImplementedException();
        }

        public static Option<T> AsOption<T>(this T it)
        {
            throw new NotImplementedException();
        }

        public static Option<TResult> Select<T, TResult>(this Option<T> it, Func<T, TResult> func)
        {
            throw new NotImplementedException();
        }

        public static void Select<T>(this Option<T> it, Action<T> func)
        {
            throw new NotImplementedException();
        }

        public static Option<TResult> Match<T, TResult>(this Option<T> it, Func<T, TResult> func, Func<TResult> func2)
        {
            throw new NotImplementedException();
        }

        public static Option<T> Where<T>(this Option<T> it, Func<T, bool> func)
        {
            throw new NotImplementedException();
        }
    }

    public struct Option<T>
    {
        private Option(T value)
        {
            throw new NotImplementedException();
        }

        public bool HasValue
        {
            get { throw new NotImplementedException(); }
        }

        public T Value
        {
            get { throw new NotImplementedException(); }
        }

        public static implicit operator Option<T>(T value)
        {
            return new Option<T>(value);
        }

        public T GetValueOrDefault(T defaultValue)
        {
            throw new NotImplementedException();
        }
    }
}